<template>
  <div class="mainDiv rowflex">
    <!-- 左侧元素区 -->
    <div class="leftFiv columnflex" style="justify-content: start;">
      <!-- 循环所有图标 -->
      <div class="element columnflex" v-for="(item, index) in elementList" :key="index">
        <img class="element_item" :src="item.address" @dragstart="handleDragStart(index)" />
      </div>
    </div>
    <!-- 画布区 -->
    <div id="rightDiv" class="rightDiv">
      <canvas id="fabric"></canvas>
    </div>
    <!-- 右键菜单 -->
    <div id="menu" class="menu-x" v-show="menuDisplay">
      <div class="menu-li" @click="bindingIdentifier()">绑定唯一标识</div>
      <div class="menu-li" @click="deleteElement()">删除</div>
    </div>
  </div>
</template>

<script>
import Vue from 'vue';
import { Canvas, Image, Point } from 'fabric';
// 创建fabric对象
const fabric = { Canvas, Image, Point };
Vue.use(fabric);
export default {
  data() {
    return {
      // 图标列表
      elementList: [{
        key: 'ancientTrees',
        address: require('@/assets/ancientTrees.png')
      },
      {
        key: 'camera',
        address: require('@/assets/camera.png')
      },
      {
        key: 'factory',
        address: require('@/assets/factory.png')
      },
      {
        key: 'fireFighting',
        address: require('@/assets/fireFighting.png')
      },
      {
        key: 'house',
        address: require('@/assets/house.png')
      },
      {
        key: 'hydrology',
        address: require('@/assets/hydrology.png')
      },
      ],
      // 画布背景图片
      canvasBackgroundImage: require('@/assets/canvasImg.jpg'),
      // 画布
      canvas: null,
      //最后一次拖动的图片
      imageAddress: '',
      //是否拖动中
      isDragging: false,
      //最后的x轴方向的位置
      lastPosX: 0,
      //最后的y轴方向的位置
      lastPosY: 0,
      //菜单是否显示
      menuDisplay: true,
      //最后一次右键选中的元素
      lastMenu: null
    }
  },
  mounted() {
    this.initCanvas();
  },
  methods: {
    //初始化画布
    initCanvas() {
      // 获取容器元素,得到父容器的宽和高
      const container = document.getElementById('rightDiv');
      // 创建一个与容器宽高一致的 canvas 对象
      this.canvas = new fabric.Canvas("fabric", {
        width: container.clientWidth,
        height: container.clientHeight,
        fireRightClick: true, // 启用右键，button的数字为3
        stopContextMenu: true, // 禁止默认右键菜单
      })
      // 设置画布的背景图片
      this.setBackground(container.clientWidth, container.clientHeight)
      //监听元素是否被下放到画布上
      this.elementPlacement()
      //监听画布拖拽,包含三个监听事件
      this.canvasDragAndDrop()
      //监听画布缩放
      this.canvasZoom()
    },
    //设置画布的背景图片
    setBackground(clientWidth, clientHeight) {
      fabric.Image.fromURL(this.canvasBackgroundImage, img => {
        // 缩放背景图片以适应画布
        // 如果背景图片宽度或高度大于画布宽度或高度
        if (img.width > clientWidth || img.height > clientHeight) {
          // 计算缩放比例
          let scaleX = clientWidth / img.width;
          let scaleY = clientHeight / img.height;
          let scale = Math.min(scaleX, scaleY);
          // 缩放背景图片
          img.scaleToWidth(img.width * scale);
          img.scaleToHeight(img.height * scale);
        } else { // 如果背景图片宽度和高度都小于或等于画布宽度和高度
          // 计算背景图片在画布中的位置
          img.left = (clientWidth - img.width) / 2;
          img.top = (clientHeight - img.height) / 2;
        }
        this.canvas.setBackgroundImage(img, this.canvas.renderAll.bind(this.canvas));
      });
    },
    //监听被拖动元素的图片
    handleDragStart(event) {
      this.imageAddress = this.elementList[event].address
    },
    //监听元素是否被下放到画布上
    elementPlacement() {
      this.canvas.on('drop', elt => {
        // 画布元素距离浏览器左侧和顶部的距离
        let offset = {
          left: this.canvas.getSelectionElement().getBoundingClientRect().left,
          top: this.canvas.getSelectionElement().getBoundingClientRect().top
        }
        // 鼠标坐标转换成画布的坐标（未经过缩放和平移的坐标）
        let point = {
          x: elt.e.x - offset.left,
          y: elt.e.y - offset.top,
        }
        // 转换后的坐标，restorePointerVpt 不受视窗变换的影响
        let pointerVpt = this.canvas.restorePointerVpt(point)
        //创建元素
        this.createElement(this.imageAddress, pointerVpt)
      });
    },
    //在画布上生成拖拽过来的元素
    createElement(imageAddress, pointerVpt) {
      fabric.Image.fromURL(imageAddress, oImg => {
        //这个地方做了一下偏移，让鼠标位置为图标的中心，真实的位置信息要还原回去
        oImg.top = pointerVpt.y - 24
        oImg.left = pointerVpt.x - 24
        this.canvas.add(oImg)
      })
    },
    //监听画布缩放
    canvasZoom() {
      this.canvas.on('mouse:wheel', opt => {
        const delta = opt.e.deltaY // 滚轮，向上滚一下是 -100，向下滚一下是 100
        let zoom = this.canvas.getZoom() // 获取画布当前缩放值
        zoom *= 0.999 ** delta
        if (zoom > 20) zoom = 20 // 限制最大缩放级别
        if (zoom < 0.01) zoom = 0.01 // 限制最小缩放级别
        // 以鼠标所在位置为原点缩放
        this.canvas.zoomToPoint({ // 关键点
          x: opt.e.offsetX,
          y: opt.e.offsetY
        },
          zoom // 传入修改后的缩放级别
        )
      })
    },
    // 监听画布拖拽，同时也监听了右键菜单
    canvasDragAndDrop() {
      // 按下鼠标事件
      this.canvas.on("mouse:down", opt => {
        console.log('mouse:down', opt);
        var evt = opt.e
        // 判断：右键，且在元素上右键
        // opt.button: 1-左键；2-中键；3-右键
        // 在画布上点击：opt.target 为 null
        if (opt.button === 3 && opt.target) {
          this.lastMenu = opt.target
          let menu = document.getElementById('menu');
          // 禁止在菜单上的默认右键事件
          menu.oncontextmenu = function (e) {
            e.preventDefault()
          }
          // 显示菜单，设置右键菜单位置
          // 获取菜单组件的宽高
          //这个地方是我自己设置的宽和高计算的，如果你之后复制过去需要修改一下
          // const menuWidth = 120
          const menuHeight = menu.childNodes.length * 40
          // 当前鼠标位置
          let pointX = opt.pointer.x
          let pointY = opt.pointer.y
          // 计算菜单出现的位置
          // 如果鼠标靠近画布底部，菜单就出现在鼠标指针上方
          if (this.canvas.height - pointY <= menuHeight) {
            pointY -= menuHeight
          }
          menu.style = `
                  visibility: visible;
                  left: ${pointX}px;
                  top: ${pointY}px;
                  z-index: 100;
                `
          // 将菜单展示
          this.menuDisplay = true
        } else {
          // 将菜单隐藏
          this.menuDisplay = false
        }
        //拖拽
        if (evt.shiftKey === true) {
          this.isDragging = true
          this.canvas.selection = false;
        }
      });
      // 移动鼠标事件
      this.canvas.on("mouse:move", opt => {
        if (this.isDragging && opt && opt.e) {
          var delta = new fabric.Point(opt.e.movementX, opt.e.movementY);
          this.canvas.relativePan(delta);
        }
      });
      // 松开鼠标事件
      this.canvas.on("mouse:up", opt => {
        console.log(opt);
        this.isDragging = false;
        this.canvas.selection = true;
      });
    },
    //删除元素
    deleteElement() {
      this.canvas.remove(this.lastMenu)
      // 将菜单隐藏
      this.menuDisplay = false
    },
    //设定唯一标识
    bindingIdentifier() {
      this.$prompt('请输入唯一编码', '编辑', {
        confirmButtonText: '确定',
        cancelButtonText: '取消'
      }).then(({
        value
      }) => {
        this.$message({
          type: 'success',
          message: '你的唯一编码是: ' + value
        });
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '取消输入'
        });
      });
      // 将菜单隐藏
      this.menuDisplay = false
    }
  }
}
</script>

<style lang="scss">
// flex横向布局
.rowflex {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
}

// flex纵向布局
.columnflex {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

// 主div
.mainDiv {
  height: calc(100vh - 50px) !important;
  width: 100%;
  min-width: 1000px;
}

/* 左侧元素区 */
.leftFiv {
  height: 100%;
  width: 6%;
  min-width: 60px;
  border-right: solid 1px #eee;

  // 元素块
  .element {
    height: 6.5%;
    width: 60%;
    min-height: 48px;
    min-width: 48px;
    margin-top: 20px;

    .element_item {
      height: 90%;
      width: 90%;
    }
  }
}

/* 右侧画布区 */
.rightDiv {
  height: 100%;
  width: 94%;
  min-width: 940px;
}

.menu-x {
  z-index: -100;
  position: absolute;
  top: 0;
  left: 0;
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
  background-color: #fff;
}

/* 菜单每个选项 */
.menu-li {
  box-sizing: border-box;
  padding: 4px 8px;
  border-bottom: 1px solid #ccc;
  cursor: pointer;
  line-height: 30px;
  height: 40px;
  width: 120px;
}

/* 鼠标经过的选项，更改背景色 */
.menu-li:hover {
  background-color: antiquewhite;
}

/* 第一个选项，顶部两角是圆角 */
.menu-li:first-child {
  border-top-left-radius: 4px;
  border-top-right-radius: 4px;
}

/* 最后一个选项，底部两角是圆角，底部不需要边框 */
.menu-li:last-child {
  border-bottom: none;
  border-bottom-left-radius: 4px;
  border-bottom-right-radius: 4px;
}
</style>